Shiny


Shiny es un paquete de R que facilita la creación de aplicaciones web interactivas (apps) directamente desde R.

Estructura de una aplicación


Las aplicaciones se escriben en un script sencillo con nombre app.R, el cual tiene dos componentes

  • Un objeto de interfaz: ui()
  • Una función del servidor: server()

El objeto ui() controla la apariencia de la aplicación, mientras que la función server() contiene las instrucciones para construir la aplicación.

library(shiny)

ui <- 
server <- 
shinyApp(ui = ui, server = server)


Crear un aplicación Shiny



Una vez abierto Shiny Web app aparecerÔ la siguiente ventana, en donde debemos especifivar la ruta en dónde se crearÔ la carpeta que contendrÔ el proyecto.


  • Single File (app.R): crearĆ” Ui y Server en un solo archivo.
  • Single File (ui.R/server.R): se crearĆ”n ambos en dos archivos separados.


Ejemplos

Descargar la carpeta Ejemplo_histograma. Con ā€˜setwd()’ setear la ruta en donde se encuentra la carpeta. Por Ćŗltimo correr el siguiente script.

library(shiny)

runApp("Ejemplo_histograma")


Otra manera de ejecutarla, es abrir el script App.R en el editor de RStudio, en donde aparecerÔ el botón Run App en la parte superior derecha del editor.


Se pueden crear aplicaciones copiando y modificando aplicaciones e Shiny ya existentes. La galería Shiny contiene una amplia variedad de apicaciones con sus correspondientes códigos. O también se pueden utilizar los ejemplos predefinidos de Shiny que se indican a continuación:

runExample("01_hello")      # a histogram
runExample("02_text")       # tables and data frames
runExample("03_reactivity") # a reactive expression
runExample("04_mpg")        # global variables
runExample("05_sliders")    # slider bars
runExample("06_tabsets")    # tabbed panels
runExample("07_widgets")    # help text and submit buttons
runExample("08_html")       # Shiny app built from HTML
runExample("09_upload")     # file upload wizard
runExample("10_download")   # file download wizard
runExample("11_timer")      # an automated timer


Por defecto, las aplicaciones Shiny se muestran en modo ā€œnormalā€, es decir, sin el script. Para que se muestre junto a la aplicación el script, se debe establecer en modo ā€œshowcaseā€ de la siguiente manera:

runApp("01_hello", display.mode = "showcase")


O también se puede ver el directorio dónde se sitúan los archivos con los códigos de estos ejemplos. Para ello se debe ejecutar la siguiente función;

system.file("examples", package="shiny")


Interfaz ui.R


Shiny usa la función fluidPage() para crear una pantalla que se ajusta automÔticamente a las dimensiones de la ventana del navegador. El diseño de la interfaz de usuario consiste en colocar elementos en esta función.


Los dos elementos mƔs comunes que se adicionan a fluidPage() son titlePanel() y sidebarLayout().
sidebarLayout() tiene dos argumentos:

  • La función sidebarPanel(): panel lateral izquierdo

  • La función mainPanel(): panel lateral derecho, en donde en general se muestra los resultados.

Se puede mover hacia el lado derecho dando el argumento opcional position = ā€œrightā€.


Controles Widgets

Son elementos web con los que el usuario puede interactuar, proporcionando una forma de intercambiar valores u opciones.

En la galerĆ­a de Widgets

Para agregar un widget() a la aplicación, se puede hacer en sidebarPanel() o en mainPanel() dentro del objeto ui.

Los primeros dos argumentos para cada widget son:

  • Nombre para el widget: el usuario no verĆ” este nombre, pero es necesario en el código, para poder acedera Ć©l.
  • Etiqueta: serĆ” el nombre que el usuario podrĆ” ver en la aplicación


Salida reactiva

En una salida reactiva se puede agregar cualquier salida de R, la cual responde automƔticamente cuando un usuario cambia el valor de un widget.

Se pueden crear resultados reactivos en un proceso de dos pasos:

  • Agregar un objeto R a la ui().
  • Indicar a Shiny cómo construir el objeto en la función server(). El objeto serĆ” reactivo al enlazarlo con un widget.


Server

Esto se debe hacer en la función server(), la cual crea un objeto de nombre output que contiene todo el código necesario para actualizar los objetos R de la aplicación.

Cada objeto R necesita tener su propia entrada, cuyo nombre debe coincidir con el elemento reactivo que se creó en la ui().


Calendario

Con el paquete calendR se pueden crear mapas de calor con calendarios mensuales o anuales. Se debe especificar un aƱo, un mes, establecer gradient = TRUE y cargar los datos.

Los colores se pueden personalizar con el argumento low.col (color para el valor mƔs bajo) y special.col (color para el valor mƔs alto).

Hay que tener en cuenta que los datos deben ser de la misma longitud que el número de días del mes o del año, según se utilice.


  • Ejemplo:
library(calendR)

covid <- read_excel("fallecidos de covid.xlsx")

covid_2021 <- covid %>% filter(dia > "2020-12-31")

calendR(year = 2021,
        special.days = covid_2021$total,
        gradient = TRUE,
        low.col = "white",
        special.col = "#FF4600",
        legend.pos = "right",
        legend.title = "Cantida de fallecidos") 

GalerƬa de calendarios

googleVis

El paquete googleVis proporciona una interfaz entre R y las herramientas de grƔficos de Google. El paquete proporciona interfaces para, entre otras, las siguientes funciones:

  • GrĆ”fico geogrĆ”fico
  • GrĆ”fico de dispersión
  • GrĆ”fico de barras
  • Histograma
  • GrĆ”fico de burbujas
  • GrĆ”fico de velas
  • GrĆ”fico de calendario


Permite a los usuarios crear pƔginas web con grƔficos interactivos basados en marcos de datos de R. Si bien utiliza las herramientas de grƔficos de Google, los datos se mantienen locales y no se suben a Google

library(googleVis)

plotdata <- gvisCalendar(data=covid, 
                         datevar="dia",
                         numvar="total",
                         options=list(
                           title="Fallecimientos por Coronavirus en Argentina",
                           calendar="{cellSize:10,
                                 yearLabel:{fontSize:20, color:'#444444'},
                                 focusedCellColor:{stroke:'red'}}",
                           width=700, height=400),
                         chartid="Calendar")
plot(plotdata)


Mapas


Una de las formas para realizar mapas es utilizando el paquete ggplot2. Para el tratamiento de datos espaciales se puede utilizar el paquete sf .


Antes de crear un grÔfico con ggplot2, es necesario tener en la sesión de R los datos necesarios para generar el mapa. Por lo tanto se deben descargar las coordenadas de los políginos del mapa a graficar. Por lo tanto, es necesario buscar en la web los archivos que tengan las coordenadas requeridas, que en general son .json.


  • Ejemplo

En este ejemplo descargamos las coordenadas de la Ciudad de Buenos Aires y graficamos el mapa

library(sf)

map_data <- st_read("https://cdn.buenosaires.gob.ar/datosabiertos/datasets/ministerio-de-educacion/barrios/barrios.geojson")
## Reading layer `barrios' from data source 
##   `https://cdn.buenosaires.gob.ar/datosabiertos/datasets/ministerio-de-educacion/barrios/barrios.geojson' 
##   using driver `GeoJSON'
## Simple feature collection with 48 features and 6 fields
## Geometry type: POLYGON
## Dimension:     XY
## Bounding box:  xmin: -58.53152 ymin: -34.70529 xmax: -58.33516 ymax: -34.52649
## Geodetic CRS:  WGS 84
ggplot() +
  geom_sf(data = map_data)


Haciendo uso del mapa graficamos las ubicaciones de las casas que se encuentran en venta de la base de datos properati

datos <- read_excel("Properati2.xlsx")

df <- datos %>%
  select(-c(start_date, end_date, created_on, title, description))

df <- df %>%
  filter(
    l1 == "Argentina",
    l2 == "Capital Federal",
    property_type == "PH",
    operation_type == "Venta")

barrio_data <- df %>% filter(lon >= -35 & lon <= -33 & lat >= -58.55 & lat <= -58.30)

ggplot() +
  geom_sf(data = map_data) +  # Plot the map
  geom_point(data = barrio_data, aes(x = lat, y = lon), color = "#E67E22", size = 0.5) +
  labs(title = "Ubicación geogrÔfica de los PH", x = "Longitud", y = "Latitud") +
  theme_bw() +
  xlab("Longitud") +
  ylab("Latitud") +
  theme(axis.title.x = element_text(size = 8),
        axis.title.y = element_text(size = 8))


Ahora vamos a graficar el precio promedio por barrio:

precio_promedio <- datos %>% group_by(l3) %>% summarise(promedio = mean(price)) %>%
                   rename(nombre = l3)

prec_promedio_capital <- inner_join(map_data, precio_promedio, by = "nombre")

ggplot(data = prec_promedio_capital) +
  geom_sf(aes(fill = promedio))


Utilizamos la librerĆ­a plotly

library(plotly)

grafico_inter <- ggplot(data = prec_promedio_capital) +
  geom_sf(aes(fill = promedio))

ggplotly(grafico_inter)


Ejercicio

Crear una aplicación Shiny similar a la que se muestra a continuación:

runApp("Nombres_Argentina")
LS0tDQp0aXRsZTogIlNoaW55IC0gQ2FsZW5kYXJpb3MgLSBNYXBhcyINCm91dHB1dDoNCiAgIGh0bWxfZG9jdW1lbnQ6DQogICAgIHRvYzogeWVzDQogICAgIGNvZGUtZm9sZDogc2hvdw0KICAgICBjb2RlLXRvb2xzOiB0cnVlDQogICAgIHRvYy1sb2NhdGlvbjogbGVmdA0KICAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgIHRoZW1lOiBmbGF0bHkNCiAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KZWRpdG9yX29wdGlvbnM6IA0KICBtYXJrZG93bjogDQogICAgd3JhcDogc2VudGVuY2UNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsIA0KICAgICAgICAgICAgICAgICAgICAgIGV2YWw9RkFMU0UpDQpvcHRpb25zKHdpZHRoID0gOTApDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkocmVhZHhsKQ0KYGBgDQoNCiMjIFNoaW55DQoNCjxjZW50ZXI+DQohW10oc2hpbnkuanBnKXt3aWR0aD0zMDBweH0NCjxicj4NCg0KPC9jZW50ZXI+DQoNClNoaW55IGVzIHVuIHBhcXVldGUgZGUgICoqUioqIHF1ZSBmYWNpbGl0YSBsYSBjcmVhY2nDs24gZGUgYXBsaWNhY2lvbmVzIHdlYiBpbnRlcmFjdGl2YXMgKGFwcHMpIGRpcmVjdGFtZW50ZSBkZXNkZSBSLiANCg0KIyMjIEVzdHJ1Y3R1cmEgZGUgdW5hIGFwbGljYWNpw7NuDQoNCjxicj4NCg0KTGFzIGFwbGljYWNpb25lcyBzZSBlc2NyaWJlbiBlbiB1biBzY3JpcHQgc2VuY2lsbG8gY29uIG5vbWJyZSAqKmFwcC5SKiosIGVsIGN1YWwgdGllbmUgZG9zIGNvbXBvbmVudGVzDQoNCi0gVW4gb2JqZXRvIGRlIGludGVyZmF6OiA8c3BhbiBzdHlsZT0iY29sb3I6IHJlZDsiPnVpKCk8L3NwYW4+DQotIFVuYSBmdW5jacOzbiBkZWwgc2Vydmlkb3I6IDxzcGFuIHN0eWxlPSJjb2xvcjogcmVkOyI+c2VydmVyKCk8L3NwYW4+DQoNCkVsIG9iamV0byAqKnVpKCkqKiBjb250cm9sYSBsYSBhcGFyaWVuY2lhIGRlIGxhIGFwbGljYWNpw7NuLCBtaWVudHJhcyBxdWUgbGEgZnVuY2nDs24gKipzZXJ2ZXIoKSoqIGNvbnRpZW5lIGxhcyBpbnN0cnVjY2lvbmVzIHBhcmEgY29uc3RydWlyIGxhIGFwbGljYWNpw7NuLg0KDQpgYGB7cn0NCmxpYnJhcnkoc2hpbnkpDQoNCnVpIDwtIA0Kc2VydmVyIDwtIA0Kc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQ0KYGBgDQoNCjxicj4NCg0KIyMjIENyZWFyIHVuIGFwbGljYWNpw7NuIFNoaW55DQoNCjxicj4NCg0KPGNlbnRlcj4NCiFbXShzaGlueTEucG5nKXt3aWR0aD02MDBweH0NCjwvY2VudGVyPg0KDQo8YnI+DQoNClVuYSB2ZXogYWJpZXJ0byAqKlNoaW55IFdlYiBhcHAqKiBhcGFyZWNlcsOhIGxhIHNpZ3VpZW50ZSB2ZW50YW5hLCBlbiBkb25kZSBkZWJlbW9zIGVzcGVjaWZpdmFyIGxhIHJ1dGEgZW4gZMOzbmRlIHNlIGNyZWFyw6EgbGEgY2FycGV0YSBxdWUgY29udGVuZHLDoSBlbCBwcm95ZWN0by4gDQoNCjxjZW50ZXI+DQohW10oc2hpbnkyLnBuZyl7d2lkdGg9NjAwcHh9DQo8L2NlbnRlcj4NCg0KPGJyPg0KDQotIFNpbmdsZSBGaWxlIChhcHAuUik6IGNyZWFyw6EgVWkgeSBTZXJ2ZXIgZW4gdW4gc29sbyBhcmNoaXZvLiANCi0gU2luZ2xlIEZpbGUgKHVpLlIvc2VydmVyLlIpOiBzZSBjcmVhcsOhbiBhbWJvcyBlbiBkb3MgYXJjaGl2b3Mgc2VwYXJhZG9zLiANCg0KPGJyPg0KDQojIyMgRWplbXBsb3MNCg0KRGVzY2FyZ2FyIGxhIGNhcnBldGEgKipFamVtcGxvX2hpc3RvZ3JhbWEqKi4gQ29uICdzZXR3ZCgpJyBzZXRlYXIgbGEgcnV0YSBlbiBkb25kZSBzZSBlbmN1ZW50cmEgbGEgY2FycGV0YS4gUG9yIMO6bHRpbW8gY29ycmVyIGVsIHNpZ3VpZW50ZSBzY3JpcHQuIA0KDQpgYGB7cn0NCmxpYnJhcnkoc2hpbnkpDQoNCnJ1bkFwcCgiRWplbXBsb19oaXN0b2dyYW1hIikNCmBgYA0KDQo8YnI+DQoNCk90cmEgbWFuZXJhIGRlIGVqZWN1dGFybGEsIGVzIGFicmlyIGVsIHNjcmlwdCAqKkFwcC5SKiogZW4gZWwgZWRpdG9yIGRlIFJTdHVkaW8sIGVuIGRvbmRlIGFwYXJlY2Vyw6EgZWwgYm90w7NuICpSdW4gQXBwKiBlbiBsYSBwYXJ0ZSBzdXBlcmlvciBkZXJlY2hhIGRlbCBlZGl0b3IuDQoNCjxicj4NCg0KU2UgcHVlZGVuIGNyZWFyIGFwbGljYWNpb25lcyBjb3BpYW5kbyB5IG1vZGlmaWNhbmRvIGFwbGljYWNpb25lcyBlIFNoaW55IHlhIGV4aXN0ZW50ZXMuIExhIGdhbGVyw61hIFtTaGlueV0oaHR0cHM6Ly9zaGlueS5wb3NpdC5jby9yL2dhbGxlcnkvKSBjb250aWVuZSB1bmEgYW1wbGlhIHZhcmllZGFkIGRlIGFwaWNhY2lvbmVzIGNvbiBzdXMgY29ycmVzcG9uZGllbnRlcyBjw7NkaWdvcy4gTyB0YW1iacOpbiBzZSBwdWVkZW4gdXRpbGl6YXIgbG9zIGVqZW1wbG9zIHByZWRlZmluaWRvcyBkZSBTaGlueSBxdWUgc2UgaW5kaWNhbiBhIGNvbnRpbnVhY2nDs246DQoNCmBgYHtyfQ0KcnVuRXhhbXBsZSgiMDFfaGVsbG8iKSAgICAgICMgYSBoaXN0b2dyYW0NCnJ1bkV4YW1wbGUoIjAyX3RleHQiKSAgICAgICAjIHRhYmxlcyBhbmQgZGF0YSBmcmFtZXMNCnJ1bkV4YW1wbGUoIjAzX3JlYWN0aXZpdHkiKSAjIGEgcmVhY3RpdmUgZXhwcmVzc2lvbg0KcnVuRXhhbXBsZSgiMDRfbXBnIikgICAgICAgICMgZ2xvYmFsIHZhcmlhYmxlcw0KcnVuRXhhbXBsZSgiMDVfc2xpZGVycyIpICAgICMgc2xpZGVyIGJhcnMNCnJ1bkV4YW1wbGUoIjA2X3RhYnNldHMiKSAgICAjIHRhYmJlZCBwYW5lbHMNCnJ1bkV4YW1wbGUoIjA3X3dpZGdldHMiKSAgICAjIGhlbHAgdGV4dCBhbmQgc3VibWl0IGJ1dHRvbnMNCnJ1bkV4YW1wbGUoIjA4X2h0bWwiKSAgICAgICAjIFNoaW55IGFwcCBidWlsdCBmcm9tIEhUTUwNCnJ1bkV4YW1wbGUoIjA5X3VwbG9hZCIpICAgICAjIGZpbGUgdXBsb2FkIHdpemFyZA0KcnVuRXhhbXBsZSgiMTBfZG93bmxvYWQiKSAgICMgZmlsZSBkb3dubG9hZCB3aXphcmQNCnJ1bkV4YW1wbGUoIjExX3RpbWVyIikgICAgICAjIGFuIGF1dG9tYXRlZCB0aW1lcg0KYGBgDQoNCjxicj4NCg0KUG9yIGRlZmVjdG8sIGxhcyBhcGxpY2FjaW9uZXMgU2hpbnkgc2UgbXVlc3RyYW4gZW4gbW9kbyDigJxub3JtYWzigJ0sIGVzIGRlY2lyLCBzaW4gZWwgc2NyaXB0LiBQYXJhIHF1ZSBzZSBtdWVzdHJlIGp1bnRvIGEgbGEgYXBsaWNhY2nDs24gZWwgc2NyaXB0LCBzZSBkZWJlIGVzdGFibGVjZXIgZW4gbW9kbyDigJxzaG93Y2FzZeKAnSBkZSBsYSBzaWd1aWVudGUgbWFuZXJhOg0KDQpgYGB7cn0NCnJ1bkFwcCgiMDFfaGVsbG8iLCBkaXNwbGF5Lm1vZGUgPSAic2hvd2Nhc2UiKQ0KYGBgDQoNCjxicj4NCg0KTyB0YW1iacOpbiBzZSBwdWVkZSB2ZXIgZWwgZGlyZWN0b3JpbyBkw7NuZGUgc2Ugc2l0w7phbiBsb3MgYXJjaGl2b3MgY29uIGxvcyBjw7NkaWdvcyBkZSBlc3RvcyBlamVtcGxvcy4gUGFyYSBlbGxvIHNlIGRlYmUgZWplY3V0YXIgbGEgc2lndWllbnRlIGZ1bmNpw7NuOw0KDQpgYGB7cn0NCnN5c3RlbS5maWxlKCJleGFtcGxlcyIsIHBhY2thZ2U9InNoaW55IikNCmBgYA0KDQo8YnI+DQoNCiMjIyBJbnRlcmZheiB1aS5SDQoNCjxicj4NCg0KU2hpbnkgdXNhIGxhIGZ1bmNpw7NuICoqZmx1aWRQYWdlKCkqKiBwYXJhIGNyZWFyIHVuYSBwYW50YWxsYSBxdWUgc2UgYWp1c3RhIGF1dG9tw6F0aWNhbWVudGUgYSBsYXMgZGltZW5zaW9uZXMgZGUgbGEgdmVudGFuYSBkZWwgbmF2ZWdhZG9yLiBFbCBkaXNlw7FvIGRlIGxhIGludGVyZmF6IGRlIHVzdWFyaW8gY29uc2lzdGUgZW4gY29sb2NhciBlbGVtZW50b3MgZW4gZXN0YSBmdW5jacOzbi4NCg0KPGJyPg0KDQpMb3MgZG9zIGVsZW1lbnRvcyBtw6FzIGNvbXVuZXMgcXVlIHNlIGFkaWNpb25hbiBhICoqZmx1aWRQYWdlKCkqKiBzb24gPHNwYW4gc3R5bGU9ImNvbG9yOiByZWQ7Ij50aXRsZVBhbmVsKCk8L3NwYW4+IHkgPHNwYW4gc3R5bGU9ImNvbG9yOiByZWQ7Ij5zaWRlYmFyTGF5b3V0KCk8L3NwYW4+LiAgDQpzaWRlYmFyTGF5b3V0KCkgdGllbmUgZG9zIGFyZ3VtZW50b3M6DQoNCi0gTGEgZnVuY2nDs24gc2lkZWJhclBhbmVsKCk6IHBhbmVsIGxhdGVyYWwgaXpxdWllcmRvDQoNCi0gTGEgZnVuY2nDs24gbWFpblBhbmVsKCk6IHBhbmVsIGxhdGVyYWwgZGVyZWNobywgZW4gZG9uZGUgZW4gZ2VuZXJhbCBzZSBtdWVzdHJhIGxvcyByZXN1bHRhZG9zLiANCg0KU2UgcHVlZGUgbW92ZXIgaGFjaWEgZWwgbGFkbyBkZXJlY2hvIGRhbmRvIGVsIGFyZ3VtZW50byBvcGNpb25hbCAqKnBvc2l0aW9uID0gInJpZ2h0IioqLg0KDQo8YnI+DQoNCiMjIyBDb250cm9sZXMgV2lkZ2V0cyANCg0KU29uIGVsZW1lbnRvcyB3ZWIgY29uIGxvcyBxdWUgZWwgdXN1YXJpbyBwdWVkZSBpbnRlcmFjdHVhciwgcHJvcG9yY2lvbmFuZG8gdW5hIGZvcm1hIGRlIGludGVyY2FtYmlhciB2YWxvcmVzIHUgb3BjaW9uZXMuDQoNCkVuIGxhIGdhbGVyw61hIGRlIFtXaWRnZXRzXShodHRwczovL3NoaW55LnBvc2l0LmNvL3IvZ2FsbGVyeS93aWRnZXRzL3dpZGdldC1nYWxsZXJ5LykNCg0KUGFyYSBhZ3JlZ2FyIHVuIHdpZGdldCgpIGEgbGEgYXBsaWNhY2nDs24sIHNlIHB1ZWRlIGhhY2VyIGVuIHNpZGViYXJQYW5lbCgpIG8gZW4gbWFpblBhbmVsKCkgZGVudHJvIGRlbCBvYmpldG8gdWkuDQoNCkxvcyBwcmltZXJvcyBkb3MgYXJndW1lbnRvcyBwYXJhIGNhZGEgd2lkZ2V0IHNvbjoNCg0KLSBOb21icmUgcGFyYSBlbCB3aWRnZXQ6IGVsIHVzdWFyaW8gbm8gdmVyw6EgZXN0ZSBub21icmUsIHBlcm8gZXMgbmVjZXNhcmlvIGVuIGVsIGPDs2RpZ28sIHBhcmEgcG9kZXIgYWNlZGVyYSDDqWwuDQotIEV0aXF1ZXRhOiBzZXLDoSBlbCBub21icmUgcXVlIGVsIHVzdWFyaW8gcG9kcsOhIHZlciBlbiBsYSBhcGxpY2FjacOzbg0KDQo8YnI+DQoNCiMjIyBTYWxpZGEgcmVhY3RpdmENCg0KDQpFbiB1bmEgc2FsaWRhIHJlYWN0aXZhIHNlIHB1ZWRlIGFncmVnYXIgY3VhbHF1aWVyIHNhbGlkYSBkZSBSLCBsYSBjdWFsIHJlc3BvbmRlIGF1dG9tw6F0aWNhbWVudGUgY3VhbmRvIHVuIHVzdWFyaW8gY2FtYmlhIGVsIHZhbG9yIGRlIHVuIHdpZGdldC4NCg0KDQpTZSBwdWVkZW4gY3JlYXIgcmVzdWx0YWRvcyByZWFjdGl2b3MgZW4gdW4gcHJvY2VzbyBkZSBkb3MgcGFzb3M6DQoNCi0gQWdyZWdhciB1biBvYmpldG8gUiBhIGxhIHVpKCkuDQotIEluZGljYXIgYSBTaGlueSBjw7NtbyBjb25zdHJ1aXIgZWwgb2JqZXRvIGVuIGxhIGZ1bmNpw7NuIHNlcnZlcigpLiBFbCBvYmpldG8gc2Vyw6EgcmVhY3Rpdm8gYWwgZW5sYXphcmxvIGNvbiB1biB3aWRnZXQuDQoNCjxicj4NCg0KIyMjIFNlcnZlcg0KDQoNCkVzdG8gc2UgZGViZSBoYWNlciBlbiBsYSBmdW5jacOzbiBzZXJ2ZXIoKSwgbGEgY3VhbCBjcmVhIHVuIG9iamV0byBkZSBub21icmUgb3V0cHV0IHF1ZSBjb250aWVuZSB0b2RvIGVsIGPDs2RpZ28gbmVjZXNhcmlvIHBhcmEgYWN0dWFsaXphciBsb3Mgb2JqZXRvcyBSIGRlIGxhIGFwbGljYWNpw7NuLg0KDQpDYWRhIG9iamV0byBSIG5lY2VzaXRhIHRlbmVyIHN1IHByb3BpYSBlbnRyYWRhLCBjdXlvIG5vbWJyZSBkZWJlIGNvaW5jaWRpciBjb24gZWwgZWxlbWVudG8gcmVhY3Rpdm8gcXVlIHNlIGNyZcOzIGVuIGxhIHVpKCkuDQoNCjxicj4NCg0KIyMgQ2FsZW5kYXJpbw0KDQpDb24gZWwgcGFxdWV0ZSAqKmNhbGVuZFIqKiBzZSBwdWVkZW4gY3JlYXIgbWFwYXMgZGUgY2Fsb3IgY29uIGNhbGVuZGFyaW9zIG1lbnN1YWxlcyBvIGFudWFsZXMuIFNlIGRlYmUgZXNwZWNpZmljYXIgdW4gYcOxbywgdW4gbWVzLCBlc3RhYmxlY2VyIGdyYWRpZW50ID0gVFJVRSB5IGNhcmdhciBsb3MgZGF0b3MuIA0KDQpMb3MgY29sb3JlcyBzZSBwdWVkZW4gcGVyc29uYWxpemFyIGNvbiBlbCBhcmd1bWVudG8gKipsb3cuY29sKiogKGNvbG9yIHBhcmEgZWwgdmFsb3IgbcOhcyBiYWpvKSB5ICoqc3BlY2lhbC5jb2wqKiAoY29sb3IgcGFyYSBlbCB2YWxvciBtw6FzIGFsdG8pLiANCg0KSGF5IHF1ZSB0ZW5lciBlbiBjdWVudGEgcXVlIGxvcyBkYXRvcyBkZWJlbiBzZXIgZGUgbGEgbWlzbWEgbG9uZ2l0dWQgcXVlIGVsIG7Dum1lcm8gZGUgZMOtYXMgZGVsIG1lcyBvIGRlbCBhw7FvLCBzZWfDum4gc2UgdXRpbGljZS4NCg0KPGJyPg0KDQotIEVqZW1wbG86DQoNCmBgYHtyIGV2YWwgPSBUUlVFfQ0KbGlicmFyeShjYWxlbmRSKQ0KDQpjb3ZpZCA8LSByZWFkX2V4Y2VsKCJmYWxsZWNpZG9zIGRlIGNvdmlkLnhsc3giKQ0KDQpjb3ZpZF8yMDIxIDwtIGNvdmlkICU+JSBmaWx0ZXIoZGlhID4gIjIwMjAtMTItMzEiKQ0KDQpjYWxlbmRSKHllYXIgPSAyMDIxLA0KICAgICAgICBzcGVjaWFsLmRheXMgPSBjb3ZpZF8yMDIxJHRvdGFsLA0KICAgICAgICBncmFkaWVudCA9IFRSVUUsDQogICAgICAgIGxvdy5jb2wgPSAid2hpdGUiLA0KICAgICAgICBzcGVjaWFsLmNvbCA9ICIjRkY0NjAwIiwNCiAgICAgICAgbGVnZW5kLnBvcyA9ICJyaWdodCIsDQogICAgICAgIGxlZ2VuZC50aXRsZSA9ICJDYW50aWRhIGRlIGZhbGxlY2lkb3MiKSANCmBgYA0KDQpbR2FsZXLDrGEgZGUgY2FsZW5kYXJpb3NdKGh0dHBzOi8vci1jb2Rlci5jb20vY2FsZW5kYXItcGxvdC1yLykNCg0KIyMjIGdvb2dsZVZpcw0KDQoNCkVsIHBhcXVldGUgZ29vZ2xlVmlzIHByb3BvcmNpb25hIHVuYSBpbnRlcmZheiBlbnRyZSBSIHkgbGFzIGhlcnJhbWllbnRhcyBkZSBncsOhZmljb3MgZGUgR29vZ2xlLiBFbCBwYXF1ZXRlIHByb3BvcmNpb25hIGludGVyZmFjZXMgcGFyYSwgZW50cmUgb3RyYXMsIGxhcyBzaWd1aWVudGVzIGZ1bmNpb25lczoNCg0KLSBHcsOhZmljbyBnZW9ncsOhZmljbw0KLSBHcsOhZmljbyBkZSBkaXNwZXJzacOzbg0KLSBHcsOhZmljbyBkZSBiYXJyYXMNCi0gSGlzdG9ncmFtYQ0KLSBHcsOhZmljbyBkZSBidXJidWphcw0KLSBHcsOhZmljbyBkZSB2ZWxhcw0KLSBHcsOhZmljbyBkZSBjYWxlbmRhcmlvDQoNCjxicj4NCg0KUGVybWl0ZSBhIGxvcyB1c3VhcmlvcyBjcmVhciBww6FnaW5hcyB3ZWIgY29uIGdyw6FmaWNvcyBpbnRlcmFjdGl2b3MgYmFzYWRvcyBlbiBtYXJjb3MgZGUgZGF0b3MgZGUgUi4gU2kgYmllbiB1dGlsaXphIGxhcyBoZXJyYW1pZW50YXMgZGUgZ3LDoWZpY29zIGRlIEdvb2dsZSwgbG9zIGRhdG9zIHNlIG1hbnRpZW5lbiBsb2NhbGVzIHkgbm8gc2Ugc3ViZW4gYSBHb29nbGUNCg0KYGBge3IgZXZhbD1UUlVFfQ0KbGlicmFyeShnb29nbGVWaXMpDQoNCnBsb3RkYXRhIDwtIGd2aXNDYWxlbmRhcihkYXRhPWNvdmlkLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBkYXRldmFyPSJkaWEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG51bXZhcj0idG90YWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG9wdGlvbnM9bGlzdCgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlPSJGYWxsZWNpbWllbnRvcyBwb3IgQ29yb25hdmlydXMgZW4gQXJnZW50aW5hIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbGVuZGFyPSJ7Y2VsbFNpemU6MTAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ZWFyTGFiZWw6e2ZvbnRTaXplOjIwLCBjb2xvcjonIzQ0NDQ0NCd9LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZm9jdXNlZENlbGxDb2xvcjp7c3Ryb2tlOidyZWQnfX0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGg9NzAwLCBoZWlnaHQ9NDAwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBjaGFydGlkPSJDYWxlbmRhciIpDQpwbG90KHBsb3RkYXRhKQ0KYGBgDQoNCjxicj4NCg0KIyMgTWFwYXMNCg0KPGJyPg0KDQpVbmEgZGUgbGFzIGZvcm1hcyBwYXJhIHJlYWxpemFyIG1hcGFzIGVzIHV0aWxpemFuZG8gZWwgcGFxdWV0ZSAqKmdncGxvdDIqKi4gDQpQYXJhIGVsIHRyYXRhbWllbnRvIGRlIGRhdG9zIGVzcGFjaWFsZXMgc2UgcHVlZGUgdXRpbGl6YXIgZWwgcGFxdWV0ZSAqKnNmKiogLg0KDQo8YnI+DQoNCkFudGVzIGRlIGNyZWFyIHVuIGdyw6FmaWNvIGNvbiBnZ3Bsb3QyLCBlcyBuZWNlc2FyaW8gdGVuZXIgZW4gbGEgc2VzacOzbiBkZSBSIGxvcyBkYXRvcyBuZWNlc2FyaW9zIHBhcmEgZ2VuZXJhciBlbCBtYXBhLiBQb3IgbG8gdGFudG8gc2UgZGViZW4gZGVzY2FyZ2FyIGxhcyBjb29yZGVuYWRhcyBkZSBsb3MgcG9sw61naW5vcyBkZWwgbWFwYSBhIGdyYWZpY2FyLiBQb3IgbG8gdGFudG8sIGVzIG5lY2VzYXJpbyBidXNjYXIgZW4gbGEgd2ViIGxvcyBhcmNoaXZvcyBxdWUgdGVuZ2FuIGxhcyBjb29yZGVuYWRhcyByZXF1ZXJpZGFzLCBxdWUgZW4gZ2VuZXJhbCBzb24gKiouanNvbioqLg0KDQo8YnI+DQoNCi0gRWplbXBsbw0KDQoNCkVuIGVzdGUgZWplbXBsbyBkZXNjYXJnYW1vcyBsYXMgY29vcmRlbmFkYXMgZGUgbGEgQ2l1ZGFkIGRlIEJ1ZW5vcyBBaXJlcyB5IGdyYWZpY2Ftb3MgZWwgbWFwYQ0KDQpgYGB7ciBldmFsPVRSVUV9DQpsaWJyYXJ5KHNmKQ0KDQptYXBfZGF0YSA8LSBzdF9yZWFkKCJodHRwczovL2Nkbi5idWVub3NhaXJlcy5nb2IuYXIvZGF0b3NhYmllcnRvcy9kYXRhc2V0cy9taW5pc3RlcmlvLWRlLWVkdWNhY2lvbi9iYXJyaW9zL2JhcnJpb3MuZ2VvanNvbiIpDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gbWFwX2RhdGEpDQpgYGANCjxicj4NCg0KSGFjaWVuZG8gdXNvIGRlbCBtYXBhIGdyYWZpY2Ftb3MgbGFzIHViaWNhY2lvbmVzIGRlIGxhcyBjYXNhcyBxdWUgc2UgZW5jdWVudHJhbiBlbiB2ZW50YSBkZSBsYSBiYXNlIGRlIGRhdG9zICoqcHJvcGVyYXRpKioNCg0KYGBge3IgZXZhbD1UUlVFfQ0KDQpkYXRvcyA8LSByZWFkX2V4Y2VsKCJQcm9wZXJhdGkyLnhsc3giKQ0KDQpkZiA8LSBkYXRvcyAlPiUNCiAgc2VsZWN0KC1jKHN0YXJ0X2RhdGUsIGVuZF9kYXRlLCBjcmVhdGVkX29uLCB0aXRsZSwgZGVzY3JpcHRpb24pKQ0KDQpkZiA8LSBkZiAlPiUNCiAgZmlsdGVyKA0KICAgIGwxID09ICJBcmdlbnRpbmEiLA0KICAgIGwyID09ICJDYXBpdGFsIEZlZGVyYWwiLA0KICAgIHByb3BlcnR5X3R5cGUgPT0gIlBIIiwNCiAgICBvcGVyYXRpb25fdHlwZSA9PSAiVmVudGEiKQ0KDQpiYXJyaW9fZGF0YSA8LSBkZiAlPiUgZmlsdGVyKGxvbiA+PSAtMzUgJiBsb24gPD0gLTMzICYgbGF0ID49IC01OC41NSAmIGxhdCA8PSAtNTguMzApDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gbWFwX2RhdGEpICsgICMgUGxvdCB0aGUgbWFwDQogIGdlb21fcG9pbnQoZGF0YSA9IGJhcnJpb19kYXRhLCBhZXMoeCA9IGxhdCwgeSA9IGxvbiksIGNvbG9yID0gIiNFNjdFMjIiLCBzaXplID0gMC41KSArDQogIGxhYnModGl0bGUgPSAiVWJpY2FjacOzbiBnZW9ncsOhZmljYSBkZSBsb3MgUEgiLCB4ID0gIkxvbmdpdHVkIiwgeSA9ICJMYXRpdHVkIikgKw0KICB0aGVtZV9idygpICsNCiAgeGxhYigiTG9uZ2l0dWQiKSArDQogIHlsYWIoIkxhdGl0dWQiKSArDQogIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpDQpgYGANCg0KPGJyPg0KDQpBaG9yYSB2YW1vcyBhIGdyYWZpY2FyIGVsIHByZWNpbyBwcm9tZWRpbyBwb3IgYmFycmlvOg0KDQoNCmBgYHtyIGV2YWw9VFJVRX0NCg0KcHJlY2lvX3Byb21lZGlvIDwtIGRhdG9zICU+JSBncm91cF9ieShsMykgJT4lIHN1bW1hcmlzZShwcm9tZWRpbyA9IG1lYW4ocHJpY2UpKSAlPiUNCiAgICAgICAgICAgICAgICAgICByZW5hbWUobm9tYnJlID0gbDMpDQoNCnByZWNfcHJvbWVkaW9fY2FwaXRhbCA8LSBpbm5lcl9qb2luKG1hcF9kYXRhLCBwcmVjaW9fcHJvbWVkaW8sIGJ5ID0gIm5vbWJyZSIpDQoNCmdncGxvdChkYXRhID0gcHJlY19wcm9tZWRpb19jYXBpdGFsKSArDQogIGdlb21fc2YoYWVzKGZpbGwgPSBwcm9tZWRpbykpDQpgYGANCjxicj4NCg0KVXRpbGl6YW1vcyBsYSBsaWJyZXLDrWEgKipwbG90bHkqKg0KDQpgYGB7ciBldmFsPVRSVUV9DQpsaWJyYXJ5KHBsb3RseSkNCg0KZ3JhZmljb19pbnRlciA8LSBnZ3Bsb3QoZGF0YSA9IHByZWNfcHJvbWVkaW9fY2FwaXRhbCkgKw0KICBnZW9tX3NmKGFlcyhmaWxsID0gcHJvbWVkaW8pKQ0KDQpnZ3Bsb3RseShncmFmaWNvX2ludGVyKQ0KYGBgDQoNCjxicj4NCg0KIyMgRWplcmNpY2lvDQoNCkNyZWFyIHVuYSBhcGxpY2FjacOzbiBTaGlueSBzaW1pbGFyIGEgbGEgcXVlIHNlIG11ZXN0cmEgYSBjb250aW51YWNpw7NuOg0KDQpgYGB7cn0NCnJ1bkFwcCgiTm9tYnJlc19BcmdlbnRpbmEiKQ0KYGBgDQoNCg==